﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Animations;
using Silvermoon.Core;
using Silvermoon.UI;
using System.Linq;
using System.Diagnostics;
using Silvermoon.Controls.Forms;
using Silvermoon.Windows;

namespace Silvermoon.Controls.Base
{
    public abstract class FormBase : CompositeControlBase, ICompositeControl
    {
        #region accessors

        public static PropertyAccessor<FormBase, float> VisibilityProperty = PropertyAccessor.Register<FormBase, float>(
            "Visibility",
            c => c.Visibility,
            (c, v) => c.Visibility = v);

        public static PropertyAccessor<FormBase, float> TranslateXFactorProperty = PropertyAccessor.Register<FormBase, float>(
            "TranslateXFactor",
            c => c.TranslateXFactor,
            (c, v) => c.TranslateXFactor = v);

        public static PropertyAccessor<FormBase, float> TranslateYFactorProperty = PropertyAccessor.Register<FormBase, float>(
            "TranslateYFactor",
            c => c.TranslateYFactor,
            (c, v) => c.TranslateYFactor = v);

        public static PropertyAccessor<FormBase, float> RotateHorizonProperty = PropertyAccessor.Register<FormBase, float>(
            "RotateHorizon",
            c => c.RotateX,
            (c, v) => c.RotateX = v);

        public static PropertyAccessor<FormBase, float> RotateVerticalProperty = PropertyAccessor.Register<FormBase, float>(
            "RotateVertical",
            c => c.RotateY,
            (c, v) => c.RotateY = v);

        #endregion
        #region fields

        private RotateTransform rotateY = new RotateTransform { Axis = RotateAxis.XAxis };
        private RotateTransform rotateX = new RotateTransform { Axis = RotateAxis.YAxis };
        private float translateXFactor, translateYFactor;
        private float visibility = 0f;
        private VisualStateManager transitionManager;

        #endregion
        #region ctors/dtors

        public FormBase()
            : base()
        {
            ControlFlags |= ControlFlags.CanPan;
            Visible = false;
            Transformation += rotateY;
            Transformation += rotateX;

            Screen.OrientationChanged += OnOrientationChanged;
        }


        protected override void Dispose(bool dispose)
        {
            Screen screen = Screen;
            if (screen != null) screen.OrientationChanged -= OnOrientationChanged;
            FormBase.VisibilityProperty.StopAnimation(this);
            base.Dispose(dispose);
        }

        #endregion
        #region methods

        protected virtual VisualStateManager GetDefaultVisibilityManager()
        {
            return FormVisibilityManager.Default;
        }

        /// <summary>
        /// Occurs when visibility value changes.
        /// </summary>
        protected virtual void OnVisibilityChanged(float oldValue)
        {
            Visible = visibility != 0f;

            if (visibility == 0f) Schedule(OnHidden);
            else if (visibility == 1f) Schedule(OnShown);
        }

        protected override void PreLayout()
        {
            base.PreLayout();
            Layouter.Layout(this, Screen.Size);
        }

        protected internal override void OnTap(TapEventArgs e)
        {
            e.Handled = true;
            e.Handled = true;
            base.OnTap(e);
        }

        protected internal override void OnBeginPan(PanEventArgs e)
        {
            e.Handled = false;
            e.Handled = true;
            base.OnBeginPan(e);
        }

        /// <summary>
        /// Raises the ViewChanged event.
        /// </summary>
        public void RaiseViewChanged(object sender)
        {
            Screen s = Screen;
            if (s != null) s.RaiseViewChanged(this);
            if (ViewChanged != null) ViewChanged(sender, EventArgs.Empty);
        }

        /// <summary>
        /// Creates and sets a <see cref="VisualStateManager"/> responsible for Hide and Show.
        /// Note that you can also use the <see cref="P:TransitionManager"/> property to apply any kind of transition.
        /// </summary>
        /// <param name="duration">Duration to show or hide.</param>
        /// <param name="showMask">Transition mask for Show.</param>
        /// <param name="hideMask">Transition mask for Hide.</param>
        public void SetTransition(int duration, TransitionMask showMask, TransitionMask hideMask)
        {
            FormTransitions.SetVisualStateManager(this, duration, showMask, hideMask);
        }

        /// <summary>
        /// Creates and sets a <see cref="VisualStateManager"/> responsible for Hide and Show.
        /// Note tha you can also use the <see cref="P:TransitionManager"/> property to apply any kind of transition.
        /// </summary>
        /// <param name="duration">Duration to show or hide.</param>
        /// <param name="showMask">Transition mask for Show.</param>
        /// <param name="hideMask">Transition mask for Hide.</param>
        [Obsolete]
        public void SetTransition(int showDuration, TransitionMask showMask, int hideDuration, TransitionMask hideMask)
        {
            FormTransitions.SetVisualStateManager(this, showMask, showDuration, hideMask, hideDuration);
        }

        [Obsolete]
        public void SetTransition(int showDuration, int showDelay, TransitionMask showMask, int hideDuration, int hideDelay, TransitionMask hideMask)
        {
            FormTransitions.SetVisualStateManager(this, showMask, showDuration, showDelay, hideMask, hideDuration, hideDelay);
        }

        protected virtual void OnOrientationChanged(object sender, EventArgs e)
        {
            RaiseEvent(OrientationChangedEvent, e);
        }

        #endregion
        #region Props

        /// <summary>
        /// Gets the windows orientation.
        /// </summary>
        public Orientation Orientation
        {
            get { return Screen != null ? Screen.Orientation : Orientation.Landscape; }
        }

        /// <summary>
        /// Gets or sets the VisualStateManager that contains the States "Hidden" and "Shown" which are processed when Hide() or Show() is invoked.
        /// Note: do not forget to animate the Visibility property from 0 to 1 and vice versa!
        /// </summary>
        public VisualStateManager TransitionManager
        {
            get
            {
                if (transitionManager == null) transitionManager = GetDefaultVisibilityManager();
                return transitionManager;
            }
            set
            {
                transitionManager = value;
            }
        }

        protected float RotateX
        {
            get { return rotateY.Angle; }
            set
            {
                rotateY.Angle = value;

                if (value >= 360f) rotateY.OrigY = 1f;
                else if (value > -360f) rotateY.OrigY = 0f;
                else rotateY.OrigY = -1f;
                Invalidate(InvalidationFlag.Transformation);
            }
        }

        protected float RotateY
        {
            get { return rotateX.Angle; }
            set
            {
                rotateX.Angle = value;
                if (value >= 360f) rotateX.OrigX = 1f;
                else if (value > -360f) rotateX.OrigX = 0f;
                else rotateX.OrigX = -1f;
                Invalidate(InvalidationFlag.Transformation);
            }
        }

        protected float TranslateXFactor
        {
            get
            {
                return translateXFactor;
            }
            set
            {
                if (translateXFactor != value)
                {
                    translateXFactor = value;
                    if (Screen != null)
                    {
                        Projection.Translate.X = (int)(value * Screen.Width);
                    }
                    Invalidate(InvalidationFlag.Transformation);
                }
            }
        }

        protected float TranslateYFactor
        {
            get
            {
                return translateYFactor;
            }
            set
            {
                if (translateYFactor != value)
                {
                    translateYFactor = value;
                    if (Screen != null)
                    {
                        Projection.Translate.Y = (int)(value * Screen.Height);
                    }
                    Invalidate(InvalidationFlag.Transformation);
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility transition of the form between 0f and 1f. where 0f means hidden and 1f visible.
        /// </summary>
        public float Visibility
        {
            get
            {
                return visibility;
            }
            set
            {
                if (value < 0f) value = 0f;
                if (value > 1f) value = 1f;
                if (visibility != value)
                {
                    float oldValue = visibility;
                    visibility = value;
                    OnVisibilityChanged(oldValue);
                }
            }
        }

        /// <summary>
        /// Gets the transition mode that the dialog is currently performing, if in a transition.
        /// You can use this value on a derived OnTransitionProgress method.
        /// </summary>
        protected TransitionMode CurrentTransition { get; private set; }

        /// <summary>
        /// Gets whether the form is visible.
        /// </summary>
        public virtual bool IsShown
        {
            get { return visibility == 1f; }
        }

        /// <summary>
        /// Gets whether the form is hidden.
        /// </summary>
        public bool IsHidden
        {
            get { return visibility == 0f; }
        }

        /// <summary>
        /// Gets whether the form is currently in a process between shown and hidden.
        /// </summary>
        public bool IsInterminate
        {
            get { return visibility > 0f && visibility < 1f; }
        }

        #endregion
        #region IForm Members

        /// <summary>
        /// Sends the window to the back.
        /// </summary>
        public void SendToBack()
        {
            Screen.SendToBack(this);
        }

        /// <summary>
        /// Brings the window into the front.
        /// </summary>
        public void BringToFront()
        {
            Screen.BringToFront(this);
        }

        /// <summary>
        /// Shows the form.
        /// </summary>
        public void Show()
        {
            Show(TransitionManager != null ? TransitionManager.GetState(VState.Shown) : null);
        }

        /// <summary>
        /// Shows the form.
        /// </summary>
        /// <param name="showState">Visual state which contains all transitions to perform, otherwise null.</param>
        public virtual void Show(VisualState showState)
        {
            if (showState == null) showState = VisualState.Empty;

            if (!IsShown)
            {
                PreLoad();
                Opacity = 255;
                Focus();

                // no animations are performed on PropertyAccessors if the control is marked as not initialized.
                // therefore this flag must be removed prior to perform any show transition:
                //if (!IsInitialized && Screen.ActiveWindow != null)
                if (!IsInitialized)
                {
                    InvalidationMask &= ~InvalidationFlag.Uninitialized;
                }
                OnShowing();
                if (showState == null && !IsInitialized)
                {
                    Visibility = 1f;
                }
                else
                {
                    PreLoad();
                    AnimateTransitionState(showState, 0f, 1f);
                }
            }
        }

        private void AnimateTransitionState(VisualState state, float? from, float to)
        {
            Transition visibility = state.GetTransition(VisibilityProperty);
            if (visibility == null)
            {
                int max = state.Transitions != null ? state.Transitions.Max(t => t.Duration + t.Delay) : 1;
                FloatTransition visibilityTransition = new FloatTransition { From = from, To = to, Duration = max, Tracking = TrackMode.Keep, EasingFunction = LinearEase.Instance };
                VisibilityProperty.Animate(this, visibilityTransition);
            }
            state.Animate(this, IsInitialized);
        }

        /// <summary>
        /// Hides the form
        /// </summary>
        public void Hide()
        {
            Hide(TransitionManager != null ? TransitionManager.GetState(VState.Hidden) : null);
        }

        /// <summary>
        /// Hides the form
        /// </summary>
        /// <param name="hideState">Visual state which contains all transitions to perform, otherwise null.</param>
        public virtual void Hide(VisualState hideState)
        {
            if (hideState == null) hideState = VisualState.Empty;

            if (ContextMenuBase.OpenedMenu != null && ContextMenuBase.OpenedMenu != this) ContextMenuBase.OpenedMenu.Hide();
            if (IsShown)
            {
                OnHiding();
                RemoveDescendantFocus();
                if (hideState == null || !IsInitialized)
                {
                    Visibility = 0f;
                }
                else
                {
                    AnimateTransitionState(hideState, null, 0f);
                }
            }
        }

        protected internal override void OnTouchDown(TouchEventArgs e)
        {
            base.OnTouchDown(e);
            e.Handled = true;
        }

        protected internal override void OnTouchUp(TouchEventArgs e)
        {
            base.OnTouchUp(e);
            e.Handled = true;
        }

        /// <summary>
        /// Occurs when the form has transitioned to hidden.
        /// </summary>
        protected virtual void OnHidden()
        {
            Screen.Detach(this);
            if (Hidden != null) Hidden(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs when the form has transitioned to shown.
        /// </summary>
        protected virtual void OnShown()
        {
            if (Shown != null) Shown(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs before a transition when the form is about to be hidden.
        /// </summary>
        protected virtual void OnHiding()
        {
            if (Hiding != null) Hiding(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs before a transition when the from is about to be shown.
        /// </summary>
        protected virtual void OnShowing()
        {
            Screen.Attach(this);
            if (Showing != null) Showing(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs after a transition when the form is shown.
        /// </summary>
        public event EventHandler Shown;

        /// <summary>
        /// Occurs after a transition when the form is hidden.
        /// </summary>
        public event EventHandler Hidden;

        /// <summary>
        /// Occurs before a transition when the from is about to be shown.
        /// </summary>
        public event EventHandler Showing;

        /// <summary>
        /// Occurs after a transition when the form is hidden.
        /// </summary>
        public event EventHandler Hiding;

        #endregion
        #region events

        public static SparseEvent OrientationChangedEvent = new SparseEvent();

        public event EventHandler OrientationChanged
        {
            add { AddHandler(OrientationChangedEvent, value); }
            remove { RemoveHandler(OrientationChangedEvent, value); }
        }


        /// <summary>
        /// Occurs if any control causes the view to be changed. 
        /// This is the case, when e.g. a scrollable control scrolls into another position.
        /// </summary>
        /// <remarks>
        /// Any control which is able to scroll and contains child controls must raise <see cref="RaiseViewChanged"/> if it causes to change the view.
        /// </remarks>
        public event EventHandler ViewChanged;

        #endregion

    }
}
